package com.teamsun.datasource;

import com.alibaba.druid.pool.DruidDataSource;
import com.teamsun.redis.RedisService;
import com.teamsun.util.Constants;
import org.apache.log4j.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>动态创建及访问多数据源</p>
 */
public class DynamicDataSource extends AbstractRoutingDataSource{

    private DBConfigService dbConfigService;

    private DataSource masterDataSource;

    private Map<Object, Object> targetDataSource = new HashMap<Object, Object>();

    private RedisService redisService;

    private static final String DEFAULT_DB_NAME = "dataSource";  // 默认数据库名

    private String redisCache = "0";

    // 数据源属性
    private int initialSize;

    private int minIdle;

    private int maxActive;

    private int maxWait;

    private int timeBetweenEvictionRunsMillis;

    private int minEvictableIdleTimeMillis;

    private boolean testWhileIdle;

    private boolean testOnBorrow;

    private boolean testOnReturn;

    private String validationQuery;

    private String filters;

    private boolean poolPreparedStatements;

    private int maxPoolPreparedStatementPerConnectionSize;

    /**
     * 主数据库类型
     */
    private String masterDBType;

    private static final Logger LOGGER = Logger.getLogger(DynamicDataSource.class);

    /**
     * 创建并获取数据源
     * @return
     */
    @Override
    protected DataSource determineTargetDataSource() {
        // 获取数据源名称
        String dbName = (String) determineCurrentLookupKey();

        // 获取默认数据源
        if(DEFAULT_DB_NAME.equals(dbName)) {
            return masterDataSource;
        }

        // 创建数据源
        String redisValue = "";

        // 如果后台服务为分布式部署，通过redisValue判断数据源是否修改
        if(Constants.YES.equals(redisCache)) {
            redisValue = redisService.getRedis(Constants.RPT_SYS + "_" + dbName);
        }
        DataSource dataSource = (DataSource) targetDataSource.get(dbName);
        try {
            if(Constants.YES.equals(redisCache)){
                if (dataSource == null || !Constants.YES.equals(redisValue)) {
                    System.out.println("******重新创建数据源[redis]******");
                    dataSource = getDataSourceByName(dbName);
                }
            } else {
                if (dataSource == null) {
                    System.out.println("******重新创建数据源******");
                    dataSource = getDataSourceByName(dbName);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return dataSource;
    }

    /**
     * 获取数据库名称，可根据获取的数据库名称查询数据库配置信息，
     * 通过配置信息动态创建数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String dbName = DatasourceHolder.getDBName();
        if(StringUtils.isEmpty(dbName)) {
            dbName = DEFAULT_DB_NAME;
        }

        DatasourceHolder.remove();
        return dbName;
    }

    @Override
    public void afterPropertiesSet() {

    }

    /**
     * 通过数据库的配置信息获取数据源
     * @param dbName 数据库名称
     * @return
     */
    public synchronized DataSource getDataSourceByName(String dbName) throws Exception {
        DruidDataSource dataSource = createDataSource(dbName);
        if(dataSource != null) {
            targetDataSource.put(dbName, dataSource);
            if(Constants.YES.equals(redisCache)) {
                redisService.setRedis(Constants.RPT_SYS + "_" + dbName, Constants.YES);
            }
        }
        return  dataSource;
    }

    /**
     * 通过数据库的配置创建数据源
     * @param dbName 数据库名称
     * @return
     */
    public DruidDataSource createDataSource(String dbName) throws Exception {
        String oriDBName = DatasourceHolder.getDBName();

        if(dbConfigService == null) {
            System.out.println("创建数据源失败[dbCfgService is null......]");
            LOGGER.debug("创建数据源失败[dbCfgService is null......]");
        }

        // 通过数据库名称查询相关的数据库配置信息
        DatasourceHolder.setDBName(DEFAULT_DB_NAME);
        DBCfg dbCfg = dbConfigService.getDBCfg(dbName);
        DatasourceHolder.setDBName(oriDBName);

        String driver = dbCfg.getDriverClass();  // 数据库驱动
        String url = dbCfg.getJdbcURL();  // 数据库连接地址
        String username = dbCfg.getUserName();  // 数据库用户名
        String password = dbCfg.getPassworld();  // 数据库密码

        LOGGER.debug("动态连接的数据库为[" + url + "|" + username + "]");

        // 创建数据源
        DruidDataSource basicDataSource = new DruidDataSource();
        basicDataSource.setUrl(url);
        basicDataSource.setUsername(username);
        basicDataSource.setPassword(password);
        basicDataSource.setInitialSize(initialSize);
        basicDataSource.setMinIdle(minIdle);
        basicDataSource.setMaxActive(maxActive);
        basicDataSource.setMaxWait(maxWait);
        basicDataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        basicDataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        basicDataSource.setTestWhileIdle(testWhileIdle);
        basicDataSource.setTestOnBorrow(testOnBorrow);
        basicDataSource.setTestOnReturn(testOnReturn);
        basicDataSource.setValidationQuery(validationQuery);
        basicDataSource.setFilters(filters);
        basicDataSource.setPoolPreparedStatements(poolPreparedStatements);
        basicDataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);

        // DruidDataSource默认连接失败后，会不断重试创建连接，设置以下属性当连接失败后不会重复尝试连接
        basicDataSource.setBreakAfterAcquireFailure(true);
        basicDataSource.setConnectionErrorRetryAttempts(0);
        basicDataSource.init();

        /*BasicDataSource basicDataSource = new BasicDataSource();
        basicDataSource.setDriverClassName(driver);
        basicDataSource.setUrl(url);
        basicDataSource.setUsername(username);
        basicDataSource.setPassword(password);
        basicDataSource.setTestWhileIdle(true);*/

        return basicDataSource;
    }

    /**
     * 删除缓存的数据源
     * @param dbName
     */
    public void removeDataSource(String dbName) {
        this.targetDataSource.remove(dbName);
        if(Constants.YES.equals(redisCache)) {
            redisService.removeRedis(Constants.RPT_SYS + "_" + dbName);
        }
    }

    public DataSource getMasterDataSource() {
        return masterDataSource;
    }

    public void setMasterDataSource(DataSource masterDataSource) {
        this.masterDataSource = masterDataSource;
    }

    public DBConfigService getDbConfigService() {
        return dbConfigService;
    }

    public void setDbConfigService(DBConfigService dbConfigService) {
        this.dbConfigService = dbConfigService;
    }

    public RedisService getRedisService() {
        return redisService;
    }

    public void setRedisService(RedisService redisService) {
        this.redisService = redisService;
    }

    public int getInitialSize() {
        return initialSize;
    }

    public void setInitialSize(int initialSize) {
        this.initialSize = initialSize;
    }

    public int getMinIdle() {
        return minIdle;
    }

    public void setMinIdle(int minIdle) {
        this.minIdle = minIdle;
    }

    public int getMaxActive() {
        return maxActive;
    }

    public void setMaxActive(int maxActive) {
        this.maxActive = maxActive;
    }

    public int getMaxWait() {
        return maxWait;
    }

    public void setMaxWait(int maxWait) {
        this.maxWait = maxWait;
    }

    public int getTimeBetweenEvictionRunsMillis() {
        return timeBetweenEvictionRunsMillis;
    }

    public void setTimeBetweenEvictionRunsMillis(int timeBetweenEvictionRunsMillis) {
        this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
    }

    public int getMinEvictableIdleTimeMillis() {
        return minEvictableIdleTimeMillis;
    }

    public void setMinEvictableIdleTimeMillis(int minEvictableIdleTimeMillis) {
        this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
    }

    public boolean isTestWhileIdle() {
        return testWhileIdle;
    }

    public void setTestWhileIdle(boolean testWhileIdle) {
        this.testWhileIdle = testWhileIdle;
    }

    public boolean isTestOnBorrow() {
        return testOnBorrow;
    }

    public void setTestOnBorrow(boolean testOnBorrow) {
        this.testOnBorrow = testOnBorrow;
    }

    public boolean isTestOnReturn() {
        return testOnReturn;
    }

    public void setTestOnReturn(boolean testOnReturn) {
        this.testOnReturn = testOnReturn;
    }

    public String getValidationQuery() {
        return validationQuery;
    }

    public void setValidationQuery(String validationQuery) {
        this.validationQuery = validationQuery;
    }

    public String getFilters() {
        return filters;
    }

    public void setFilters(String filters) {
        this.filters = filters;
    }

    public boolean isPoolPreparedStatements() {
        return poolPreparedStatements;
    }

    public void setPoolPreparedStatements(boolean poolPreparedStatements) {
        this.poolPreparedStatements = poolPreparedStatements;
    }

    public int getMaxPoolPreparedStatementPerConnectionSize() {
        return maxPoolPreparedStatementPerConnectionSize;
    }

    public void setMaxPoolPreparedStatementPerConnectionSize(int maxPoolPreparedStatementPerConnectionSize) {
        this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
    }

    public String getRedisCache() {
        return redisCache;
    }

    public void setRedisCache(String redisCache) {
        this.redisCache = redisCache;
    }

    public String getMasterDBType() {
        return masterDBType;
    }

    public void setMasterDBType(String masterDBType) {
        this.masterDBType = masterDBType;
    }
}
